// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/trace_event/blame_context.h"

#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"

namespace base {
namespace trace_event {

    BlameContext::BlameContext(const char* category,
        const char* name,
        const char* type,
        const char* scope,
        int64_t id,
        const BlameContext* parent_context)
        : category_(category)
        , name_(name)
        , type_(type)
        , scope_(scope)
        , id_(id)
        , parent_scope_(parent_context ? parent_context->scope() : nullptr)
        , parent_id_(parent_context ? parent_context->id() : 0)
        , category_group_enabled_(nullptr)
        , weak_factory_(this)
    {
        DCHECK(!parent_context || !std::strcmp(name_, parent_context->name()))
            << "Parent blame context must have the same name";
    }

    BlameContext::~BlameContext()
    {
        DCHECK(thread_checker_.CalledOnValidThread());
        DCHECK(WasInitialized());
        TRACE_EVENT_API_ADD_TRACE_EVENT(
            TRACE_EVENT_PHASE_DELETE_OBJECT, category_group_enabled_, type_, scope_,
            id_, 0, nullptr, nullptr, nullptr, nullptr, TRACE_EVENT_FLAG_HAS_ID);
        trace_event::TraceLog::GetInstance()->RemoveAsyncEnabledStateObserver(this);
    }

    void BlameContext::Enter()
    {
        DCHECK(WasInitialized());
        TRACE_EVENT_API_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_ENTER_CONTEXT,
            category_group_enabled_, name_, scope_, id_,
            0 /* num_args */, nullptr, nullptr, nullptr,
            nullptr, TRACE_EVENT_FLAG_HAS_ID);
    }

    void BlameContext::Leave()
    {
        DCHECK(WasInitialized());
        TRACE_EVENT_API_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_LEAVE_CONTEXT,
            category_group_enabled_, name_, scope_, id_,
            0 /* num_args */, nullptr, nullptr, nullptr,
            nullptr, TRACE_EVENT_FLAG_HAS_ID);
    }

    void BlameContext::TakeSnapshot()
    {
        DCHECK(thread_checker_.CalledOnValidThread());
        DCHECK(WasInitialized());
        if (!*category_group_enabled_)
            return;
        std::unique_ptr<trace_event::TracedValue> snapshot(
            new trace_event::TracedValue);
        AsValueInto(snapshot.get());
        static const char* kArgName = "snapshot";
        const int kNumArgs = 1;
        unsigned char arg_types[1] = { TRACE_VALUE_TYPE_CONVERTABLE };
        std::unique_ptr<trace_event::ConvertableToTraceFormat> arg_values[1] = {
            std::move(snapshot)
        };
        TRACE_EVENT_API_ADD_TRACE_EVENT(TRACE_EVENT_PHASE_SNAPSHOT_OBJECT,
            category_group_enabled_, type_, scope_, id_,
            kNumArgs, &kArgName, arg_types, nullptr,
            arg_values, TRACE_EVENT_FLAG_HAS_ID);
    }

    void BlameContext::OnTraceLogEnabled()
    {
        DCHECK(WasInitialized());
        TakeSnapshot();
    }

    void BlameContext::OnTraceLogDisabled() { }

    void BlameContext::AsValueInto(trace_event::TracedValue* state)
    {
        DCHECK(WasInitialized());
        if (!parent_id_)
            return;
        state->BeginDictionary("parent");
        state->SetString("id_ref", StringPrintf("0x%" PRIx64, parent_id_));
        state->SetString("scope", parent_scope_);
        state->EndDictionary();
    }

    void BlameContext::Initialize()
    {
        DCHECK(thread_checker_.CalledOnValidThread());
        category_group_enabled_ = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_);
        TRACE_EVENT_API_ADD_TRACE_EVENT(
            TRACE_EVENT_PHASE_CREATE_OBJECT, category_group_enabled_, type_, scope_,
            id_, 0, nullptr, nullptr, nullptr, nullptr, TRACE_EVENT_FLAG_HAS_ID);
        trace_event::TraceLog::GetInstance()->AddAsyncEnabledStateObserver(
            weak_factory_.GetWeakPtr());
        TakeSnapshot();
    }

    bool BlameContext::WasInitialized() const
    {
        return category_group_enabled_ != nullptr;
    }

} // namespace trace_event
} // namespace base
